import java.util.Random;

public class Game 
{
    /* Constantes para as cores do jogador */
    public final static int INVALID_COLOR = 0; // Cor nao definida
    public final static int GREEN = 1;
    public final static int RED = 2;
    public final static int BLUE = 3;
    public final static int YELLOW = 4;
    public final static String STRING_GREEN = "VERDE";
    public final static String STRING_RED = "VERMELHO";
    public final static String STRING_YELLOW = "AMARELO";
    public final static String STRING_BLUE = "AZUL";
    
    /* Constantes para o estado do jogo */
    public final static int STATE_NOT_RUNNING = -2;
    public final static int STATE_FIND_FIRST_PLAYER = -1;
    public final static int STATE_DICE_NOT_THROWN = 0;
    public final static int STATE_DICE_THROWN = 1;
    
    /* Constantes que definem o tipo de casa no tabuleiro */
    public final static int UNIQUE_SPACE = 0;
    public final static int SHARED_SPACE = 1;
    
    /* Outras constantes */
    public final static int MAX_DICE = 6; /* Numero maximo a ser lancado no dado */
    public final static int MAX_PLAYERS = 4; /* Numero maximo de jogadores */
    public final static int MAX_PAWNS = 4; /* Numero maximo de peoes por jogador */
    public final static int LENGHT_LEADERBOARD = 4; /* Dimensao do array podio */ 
    public final static int INITIAL_POSITION = 0; /* Casa inicial de cada peao antes de serem acrescentados ao tabuleiro */
    public final static int START_POSITION = 1; /* Casa inicial dos peoes quanso sao acrescentados ao tabuleiro */
    public final static int NUM_OF_STATES = 2; /* Numero de estados na rotacao de estados */
    public final static int MULTIPLIER = 13; /* Multiplo 13 para o calculo da diferenca de cada jogador */
    public final static int TRACK_LENGHT = 58; /* Numero de casas totais a percorrer */
    public final static int TRACK_FINAL_SEGMENT = 52; /* Numero da casa final comum a todas as cores */
    public final static int ARRAY_OFFSET = 1; /* Calcula diferenca dos indices de array */
    
    /* Variaveis de instancia */
    private int currentState; /* Estado de jogo actual */
    private int currentPlayer; /* Jogador actual */
    private int firstPlayer; /* Jogador a jogar primeiro, determinado por o comando LID */
    private int diceNumber; /* Valor do dado lancado */
    private int maxDiceNumber; /* Valor temporario para determinar o primeiro jogador */
    private int counterLeaderboard; /* Contador para o vector podio */
    private ColPlayers colPlayers; /* Objecto que contem o vector de jogadores e os seus metodos */
    private Player[] leaderBoard; /* Vector para o podio */
    
    /**
     * 
     */
    public Game() 
    {
        newGame();
    }
    
    /**
     * 
     */
    public final void newGame()
    {
        colPlayers = new ColPlayers(); 
        currentState = STATE_NOT_RUNNING;
        currentPlayer = GREEN;
        firstPlayer = -1;
        diceNumber = -1;
        maxDiceNumber = -1;
        counterLeaderboard = 0;
        leaderBoard = new Player[LENGHT_LEADERBOARD];
    }
    
    /**
     * 
     * @param color
     * @param name 
     */
    public void addPlayer(int color, String name)
    {
        colPlayers.addPlayer(color, name);
    }
    
    /**
     * 
     * @param color
     * @return 
     */
    public Player getPlayer(int color)
    {
        return colPlayers.getPlayer(color);
    }
    
    /**
     * 
     * @return 
     */
    public boolean hasAllPlayersRegistered()
    {
        return colPlayers.hasAllPlayersRegistered();
    }
    
    /**
     * 
     * @param color
     * @return 
     */
    private boolean hasPlayerFinished(int color)
    {
        return colPlayers.getPlayer(color).getPawnFinishCount() == MAX_PAWNS;
    }
    
    /**
     * 
     * @param color 
     */
    public void addPlayerToLeaderboard(int color)
    {
        leaderBoard[counterLeaderboard++] = colPlayers.getPlayer(color);
        
        if (counterLeaderboard == (LENGHT_LEADERBOARD - 1))
        {
            int player = findLastPlayerOfLeaderboard();
            leaderBoard[counterLeaderboard++] = colPlayers.getPlayer(player);
        }
    }
    
    /**
     * 
     * @param position
     * @return 
     */
    public Player getPlayerFromLeaderboard(int position)
    {
        return leaderBoard[position];
    }
    
    /**
     * 
     * @param color
     * @return 
     */
    public boolean isPlayerInLeaderboard(int color)
    {
        boolean toReturn = false;
        
        for (int i = 0; i < counterLeaderboard; i++)
            if (leaderBoard[i].getColor() == color)
                toReturn = true;
        
        return toReturn;
    }
    
    /**
     * 
     * @return 
     */
    public boolean isLeaderboardComplete()
    {
        return counterLeaderboard == LENGHT_LEADERBOARD;
    }
    
    /**
     * 
     * @return 
     */
    public int getCounterLeaderboard()
    {
        return counterLeaderboard;
    }
    
    /**
     * 
     * @return 
     */
    public int findLastPlayerOfLeaderboard()
    {
        int toReturn = -1;
        
        for (int i = 1; i <= MAX_PLAYERS; i++)
            if (!isPlayerInLeaderboard(i))
                toReturn = i;
        
        return toReturn;
    }
    
    /**
     * 
     */
    public void startGame()
    {
        changeState();
    }
 
    /**
     * 
     * @return 
     */
    public int getState()
    {
        return currentState;
    }
    
    /**
     * 
     * @param currentState 
     */
    public void setState(int currentState)
    {
        this.currentState = currentState;
    }
    
    /**
     * 
     */
    private void changeState()
    {
        currentState = (currentState + 1) % NUM_OF_STATES;
    }
    
    /**
     * 
     * @return 
     */
    public int getCurrentPlayer()
    {
        return currentPlayer;
    }
    
    /**
     * 
     * @param currentPlayer 
     */
    public void setCurrentPlayer(int currentPlayer)
    {
        this.currentPlayer = currentPlayer;
    }
    
    /**
     * 
     */
    private void changeCurrentPlayer()
    {
        do
        {
            currentPlayer = (currentPlayer % MAX_PLAYERS) + 1;
        } while (isPlayerInLeaderboard(currentPlayer));
    }
    
    /**
     * 
     * @param pawnNumber
     * @return 
     */
    public boolean startPawn(int pawnNumber)
    {
        Player p = colPlayers.getPlayer(currentPlayer); 
        boolean toReturn = false;
        
        if ((diceNumber == MAX_DICE) && !isSpaceOccupied(START_POSITION, SHARED_SPACE) 
                && (p.getPawn(pawnNumber).getPosition() == INITIAL_POSITION))
        {
            p.getPawn(pawnNumber).setPosition(START_POSITION);
            toReturn = true;
        }
        
        skipTurn();

        return toReturn;
    }

    /**
     * 
     * @param pawnId
     * @return 
     */
    public int movePawn(int pawnId)
    {
        Player p = colPlayers.getPlayer(currentPlayer); 
        int pawnPosition = p.getPawn(pawnId).getPosition();
        int newPawnPositon = pawnPosition + diceNumber;
        
        if (pawnPosition != TRACK_LENGHT && pawnPosition != INITIAL_POSITION)
        {
            if (newPawnPositon == TRACK_LENGHT)
                p.getPawn(pawnId).setPosition(TRACK_LENGHT);
            else if ((pawnPosition >= TRACK_FINAL_SEGMENT) && (newPawnPositon <= TRACK_LENGHT))
                p.getPawn(pawnId).setPosition(newPosition(pawnPosition, UNIQUE_SPACE));
            else if (newPawnPositon <= TRACK_FINAL_SEGMENT)
                p.getPawn(pawnId).setPosition(newPosition(pawnPosition, SHARED_SPACE));

            if (newPawnPositon == TRACK_LENGHT)
                pawnHasFinished();
        }
        
        if (!isLeaderboardComplete())
            skipTurn();
        
        return p.getPawn(pawnId).getPosition();
    }
    
    /**
     * 
     */
    private void pawnHasFinished()
    {
        colPlayers.getPlayer(currentPlayer).incPawnFinishCount();
        if (hasPlayerFinished(currentPlayer))
            addPlayerToLeaderboard(currentPlayer);
    }
      
    /**
     * 
     * @param pawnPosition
     * @param type
     * @return 
     */
    private int newPosition(int pawnPosition, int type)
    {
        for (int i = diceNumber; i > 0; i--)
        {
            if (!isSpaceOccupied((pawnPosition + i), type))
                return pawnPosition + i;
        }
        
        return pawnPosition;
    }
    
    /**
     * 
     * @param pos
     * @param type
     * @return 
     */
    private boolean isSpaceOccupied(int pos, int type)
    {
        if (type == UNIQUE_SPACE)
        {
            for (int x = 1; x <= MAX_PAWNS; x++)
            {
                if (colPlayers.getPlayer(currentPlayer).getPawn(x).getPosition() == pos)
                    return true; 
            }
        }
        else
        {
            for (int i = 1; i <= MAX_PLAYERS; i++)
            {
                for (int x = 1; x <= MAX_PAWNS; x++)
                {
                    if (colPlayers.getPlayer(i).getPawn(x).getPosition() == calculatePosition(pos, i))
                        return true; 
                }
            }
        }
                    
        return false;
    }
    
    /**
     * 
     * @param pos
     * @param color
     * @return 
     */
    private int calculatePosition(int pos, int color)
    {
        int mult = ((currentPlayer - color) < 0) ? ((currentPlayer - color) + MAX_PLAYERS) : (currentPlayer - color);
        int sum = (mult * MULTIPLIER) + pos;

        return mult != 0 ? (sum > TRACK_FINAL_SEGMENT ? sum - TRACK_FINAL_SEGMENT : sum) : pos;
    }
    
    /**
     * 
     */
    public void skipTurn()
    {
        changeCurrentPlayer();
        changeState();
    }
    
    /**
     * 
     * @return 
     */
    public int getDiceNumber()
    {
        return diceNumber;
    }
    
    /**
     * 
     * @param diceNumber 
     */
    public void setDiceNumber(int diceNumber)
    {
        this.diceNumber = diceNumber;
    }
       
    /**
     * 
     * @param initialThrow
     * @return 
     */
    public int throwDice(boolean initialThrow)
    {
        Random n = new Random();
        diceNumber = n.nextInt(MAX_DICE) + 1;

        if (!initialThrow)
            changeState();
        else
            initialThrowDice();
        
        return diceNumber;
    }
    
    /**
     * 
     */
    private void initialThrowDice() 
    {
        if (diceNumber > maxDiceNumber) 
        {
            maxDiceNumber = diceNumber;
            firstPlayer = currentPlayer;
        }

        if (currentPlayer == YELLOW) 
        {
            currentPlayer = firstPlayer;
            changeState();
        } 
        else 
            changeCurrentPlayer();
    }
    
    /**
     * 
     * @param color
     * @return 
     */
    public static int processColor(String color)
    {
        int toReturn;
        
        switch (color)
        {
            case STRING_GREEN:
                toReturn = GREEN;
                break;
            case STRING_RED:
                toReturn = RED;
                break;
            case STRING_BLUE:
                toReturn = BLUE;
                break;
            case STRING_YELLOW: 
                toReturn = YELLOW;
                break;
            default:
                toReturn = INVALID_COLOR;
        }
        
        return toReturn;
    }

    /**
     * 
     * @param color
     * @return 
     */
    public static String processColor(int color)
    {
        String toReturn; 
        
        switch (color)
        {
            case GREEN:
                toReturn = STRING_GREEN;
                break;
            case RED:
                toReturn = STRING_RED;
                break;
            case BLUE:
                toReturn = STRING_BLUE;
                break;
            case YELLOW: 
                toReturn = STRING_YELLOW;
                break;
            default:
                toReturn = "";
        }
        
        /* default necessario? nunca vai chegar lá */
        
        return toReturn;
    }
    
    /**
     * 
     * @param color
     * @return 
     */
    public static boolean isColor(String color)
    {
        return processColor(color) != INVALID_COLOR;
    }
    
    /**
     * 
     * @param pawnNumber
     * @return 
     */
    public static boolean isPawn(int pawnNumber)
    {
        return pawnNumber >= 1 && pawnNumber <= MAX_PAWNS;
    }
}